package br.org.bertol.mestrado;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.optimisation.Unit;
import br.org.bertol.mestrado.engine.optimisation.hillclimbing.Path;

public class Verify {

    private List<List<Integer>> strongConstraint = new ArrayList<List<Integer>>();

    private ArrayList<Integer>  classes          = new ArrayList<Integer>();

    private boolean             systemEnable     = false;

    private Objective[]         objectives;

    public Verify(final Objective[] objectives, final String source,
        final String... sistemas) {
        try {
            this.objectives = objectives;

            uploadSystem(source, sistemas);

            // instancia classes
            classes = discoverClasses(strongConstraint.size());

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Veririca a lista de precedentes para cada n� do caminho. Caso ocorra
     * alguma viola��o, invalida o caminho.
     * @param caminho
     * @see Path
     */
    public final void verifyStrongConstraint(final Unit caminho,
            final boolean fullCheck) {
        final List<Integer> rota = caminho.getRoute();

        boolean hasPrecedent = true;

        // verifica se � preciso realizar verifica��o full das preced�ncias
        if (fullCheck) {
            // verifica n� a n� se foi quebrada alguma preced�ncia
            final Iterator<Integer> iterator = rota.iterator();

            for (int x = 0; iterator.hasNext(); x++) {
                int classe = iterator.next();

                // recupera a lista de restri��es fortes do n� da rota
                // -1 para acertar a posi��o no array
                List<Integer> precedentes = strongConstraint.get(classe - 1);

                // verifica se houve quebra de precedencia
                if (!rota.subList(0, x + 1).containsAll(precedentes)) {
                    hasPrecedent = false;
                    break;
                }
            }
        } else {
            // pega o �ltimo elemento da lista
            int x = rota.get(rota.size() - 1);

            // recupera a lista de precedentes do n� da rota
            // -1 para acertar a posi��o no array
            List<Integer> precedentes = strongConstraint.get(x - 1);

            if (!rota.containsAll(precedentes)) {
                hasPrecedent = false;
            }
        }

        // avalia se referencias fortes foram quebradas
        // caso haja quebra na precedencia, marca o caminho como invi�vel
        // caso contr�rio invalida o caminho e termina o processo
        if (!hasPrecedent) {
            caminho.setValidPath(hasPrecedent);
        } else {
            caminho.setValidPath(hasPrecedent);
        }

    }

    /**
     * Veririca a lista de precedentes para cada n� do caminho. Caso ocorra
     * alguma viola��o, aumenta o peso do caminho.
     * @param caminho
     * @see Path
     */
    public final void verifyWeekConstraint(final Unit caminho,
            final boolean fullCheck, final Objective objective) {
        final List<Integer> rota = caminho.getRoute();

        // verifica todas as depend�ncias para esta solu��o
        // alterada pelo hill climbing
        if (fullCheck) {
            final Iterator<Integer> iterator = rota.iterator();

            for (int x = 0; iterator.hasNext(); x++) {
                int classe = iterator.next();

                // recupera a lista de restri��es fracas do n� da rota
                // -1 para acertar a posi��o no array
                List<Integer> weekConstraints = objective.getWeekConstraint()
                        .get(classe - 1);

                // verifica quantas depend�ncias foram violadas
                for (ListIterator<Integer> weekconstraint = weekConstraints
                        .listIterator(); weekconstraint.hasNext();) {
                    int value = weekconstraint.nextIndex();
                    weekconstraint.next();
                    // verifica se essa depend�ncia est� na lista
                    if (!rota.subList(0, x + 1).contains(value) && value > 0) {
                        caminho.addHeigtht(objective, objective
                                .getWeekConstraintHeight(classe, value));
                    }
                }
            }
        }
        // verifica��o padr�o de depend�ncias para busca greedy
        else {
            int classe = rota.get(rota.size() - 1);

            // recupera a lista de depend�ncias do n� da rota
            // -1 para acertar a posi��o no array
            List<Integer> weekConstraints = objective.getWeekConstraint()
                    .get(
                         classe - 1);

            // caso n�o tenha todas as depend�ncias respeitadas
            if (!rota.containsAll(weekConstraints)) {
                // verifica quantas depend�ncias foram violadas
                for (ListIterator<Integer> weekconstraint = weekConstraints
                        .listIterator(); weekconstraint.hasNext();) {
                    int value = weekconstraint.nextIndex();
                    weekconstraint.next();
                    // verifica se essa depend�ncia est� na lista
                    if (!rota.contains(value) && value > 0) {
                        caminho.addHeigtht(objective, objective
                                .getWeekConstraintHeight(classe, value));
                    }
                }
            }
        }
    }

    private void uploadSystem(final String source, final String... sistemas)
            throws IOException {
        systemEnable = true;

        for (String sistema : sistemas) {
            strongConstraint = Objective.loadStrongConstraint(source, sistema);

            // para cada sistema verifica se a matrix de preced�ncia foi
            // carregada
            if (strongConstraint.size() == 0) {
                Logger
                        .getLogger(this.getClass())
                        .error(
                               "Sistema "
                        + sistema
                        + " deve ter a matrix de precedência definida.");
                systemEnable = false;
                break;
            } else {
                for (Objective objective : objectives) {

                    objective.loadWeekConstraint(source, sistema);

                    if (objective.getWeekConstraint().size() == 0) {
                        Logger.getLogger(this.getClass())
                                .error(
                                       "Sistema " + sistema
                                + " deve ter a matrix de "
                                + objectives.toString()
                                + " definida.");
                        systemEnable = false;
                        break;
                    }

                }
            }
        }
    }

    private ArrayList<Integer> discoverClasses(final int num_classes) {
        final ArrayList<Integer> classes = new ArrayList<Integer>(num_classes);

        // instancias as classes do problema
        for (int i = 0; i < num_classes; i++) {
            classes.add(i + 1);
        }

        return classes;
    }

    /**
     * @return the classes
     */
    public final ArrayList<Integer> getClasses() {
        return classes;
    }

    /**
     * @return the height
     */
    public final Objective[] getObjectives() {
        return objectives;
    }

    /**
     * @return the systemEnable
     */
    public final boolean isSystemEnable() {
        return systemEnable;
    }
}
